#!/usr/bin/env python

import sys
import getopt
import getpass
import sqlite3

try:
	from xml.etree import ElementTree # for Python 2.5 users
except ImportError:
	from elementtree import ElementTree
import gdata.calendar.service
import gdata.service
import atom.service
import gdata.calendar
import atom


def iphone2unixtime(timestamp):
	return timestamp + 978307200

def unixtime2iphone(timestamp):
	return timestamp - 978307200

def eventEntry2IPhoneEvent(e, tz):
	from xsdate import parse_datetime

	ie = iPhoneCalendarEvent()
	ie.id = e.id.text
	ie.summary = e.title.text
	ie.description = e.content.text
	ie.location = e.where[0].value_string
	ie.timezone = tz

	# TODO: implement support to events that repeat...

	when = []
	for w in e.when:
		st = unixtime2iphone(parse_datetime(w.start_time))
		et = unixtime2iphone(parse_datetime(w.end_time))
		when.append((st, et,))
	ie.when = when

	ie.start_date = ie.end_date = None
	if len(when) > 0:
		ie.start_date = when[0][0]
		ie.end_date = when[len(when)-1][1]

	return ie

class iPhoneCalendarEvent:
	__atribs = {}
	
	def __setattr__(self, name, value):
		self.__atribs[name] = value
	
	def __getattr__(self, name):
		return self.__atribs[name]

class iPhoneCalendar:
	def __init__(self, dbpath=''):
		self.__calendars = []

		self.dbconn = sqlite3.connect(dbpath)
		self.dbconn.row_factory = sqlite3.Row
		self.__setup()
		self.__cacheCalendars()

	def __cacheCalendars(self):
		c = self.dbconn.cursor()
		c.execute('select rowid, title, read_only from Calendar')
		self.__calendars = c.fetchall()
	
	def __alreadySetup(self):
		c = self.dbconn.cursor()
		try:
			c.execute('select gid,gstatus from Event limit 0')
		except:
			return False
		else:
			return True

	def __setup(self):
		if not self.__alreadySetup():
			c = self.dbconn.cursor()
			c.execute('alter table Event add gid')
			c.execute('alter table Event add gstatus')

	def __eventExists(self, calendarId, eventId):
		c = self.dbconn.cursor()
		c.execute('select rowid from Event where gid=? and calendar_id=?', [eventId, calendarId])
		events = c.fetchall()
		return len(events) > 0

	def __insertEvent(self, calendarId, event):
		c = self.dbconn.cursor()
		values = [calendarId,event.timezone,event.summary,event.location,event.description,event.start_date,event.end_date,event.id,0]
		c.execute('insert into Event(calendar_id,start_tz,summary,location,description,start_date,end_date,gid,gstatus) values (?,?,?,?,?,?,?,?,?)', values)

	def __updateEvent(self, calendarId, event):
		c = self.dbconn.cursor()
		values = [calendarId,event.timezone,event.summary,event.location,event.description,event.start_date,event.end_date,0,event.id]
		c.execute('update Event set calendar_id=?,start_tz=?,summary=?,location=?,description=?,start_date=?,end_date=?,gstatus=? where GCalId = ?', values)
	
	def getCalendar(self, calendarName):
		calendars = filter(lambda t: t['title'] == calendarName, self.__calendars)
		if len(calendars) == 0:
			return None
		return calendars[0]
	
	def createCalendar(self, calendarName):
		raise 'FeatureNotImplementedYet'
	
	# event is an instance of iPhoneCalendarEvent
	def addEvent(self, calendarName, event):
		calendar = self.getCalendar(calendarName)
		if calendar is None:
			raise 'CalendarNotExists'

		if calendar['read_only'] != 0:
			raise 'CalendarIsReadOnly'

		calendarId = calendar['rowid']
		if self.__eventExists(calendarId, event.id):
			self.__updateEvent(calendarId, event)
		else:
			self.__insertEvent(calendarId, event)
	
	def dbCommit(self):
		self.dbconn.commit()



class GoogleCalendarSync:
	def __init__(self):
		# This script needs to run at /var/root/Library/Calendar/
		self.iphone_calendar = iPhoneCalendar('Calendar.sqlitedb')

	def auth(self, username, password, ssl=False):
		calendar_service = gdata.calendar.service.CalendarService()
		calendar_service.ssl = ssl
		calendar_service.email = username
		calendar_service.password = password
		calendar_service.source = 'iPhone-Google-Calendar-syncer-alpha'
		calendar_service.ProgrammaticLogin()
		self.calendar_service = calendar_service

	# At the moment the feature "from google calendar X to mobile calendar Y"
	# isnt working, and isnt top priority since mobile calendar doesnt
	# support it.
	def update(self, gcalname, icalname):
		print 'update...'
		
		feed = self.calendar_service.GetCalendarEventFeed()
		for e in feed.entry:
			# FIX: didnt find a way to get the calendar timezone
			# without another request to the server...
			event = eventEntry2IPhoneEvent(e, 'America/Sao_Paulo')
			self.iphone_calendar.addEvent(icalname, event)

		self.iphone_calendar.dbCommit()
	
	def commit(self, gcalname, icalname):
		raise 'FeatureNotImplementedYet'
	
	def sync(self, gcalname, icalname):
		self.update(gcalname, icalname)
		self.commit(gcalname, icalname)
	

def die(msg, sc=-1):
	print msg
	sys.exit(sc)

def main(username, password, gcalname='default', icalname='default', kind_sync='sync'):
	if not kind_sync in ['sync', 'commit', 'update']:
		raise Exception('Invalid kind of synchronization "%s"' % kind_sync)

	gsync = GoogleCalendarSync()
	gsync.auth(username, password)

	getattr(gsync, kind_sync)(gcalname, icalname)


if __name__ == '__main__':
	try:
		opts, args = getopt.getopt(sys.argv[1:], 'u:g:i:', ['sync','commit','update'])
	except getopt.GetoptError, e:
		print '%s: %s' % (e.opt, e.msg)
		sys.exit(-1)
	
	# Put default values
	username, password, gcalname, icalname, kind_sync = None, None, 'default', 'Default', 'update'

	# Given options
	specified_opts = map(lambda x: x[0], opts)

	# Check the required parameters
	if not '-u' in specified_opts:
		die('You need to specify an username (-u)')
	
	for o, a in opts:
		if o == '-u':
			username = a
		elif o == '-f':
			gcalname = a
		elif o == '-t':
			icalname = a
		elif o == '--commit':
			kind_sync = 'commit'
		elif o == '--update':
			kind_sync = 'update'

	if '--update' in specified_opts and '--commit' in specified_opts:
		kind_sync = 'sync'
	
	password = getpass.getpass('Please enter the password for user "%s": ' % username)

	# Lets sync!
	main(username, password, gcalname, icalname, kind_sync)

